home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 4.iso / public / GNU / emacs.inst / emacs19.idb / usr / gnu / lib / emacs / site-lisp / expire-kill.el.z / expire-kill.el
Encoding:
Text File  |  1994-08-02  |  30.1 KB  |  775 lines

  1. ;; -*-Emacs-Lisp-*-
  2. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  3. ;;
  4. ;; File:         expire-kill.el
  5. ;; RCS:          $Id: expire-kill.el,v 2.5 1993/11/23 08:47:41 liblit Exp $
  6. ;; Description:  Expiring kill patterns for GNUS
  7. ;; Author:       Ben Liblit, liblit@z-code.com
  8. ;; Created:      Wed Mar 2 1993
  9. ;; Modified:     Tue Nov 23 00:45:49 1993 (Ben) liblit@z-code.com
  10. ;;
  11. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  12.  
  13. ;;; Copyright (C) 1993  Ben Liblit.
  14. ;;;
  15. ;;; Author: Ben Liblit (liblit@z-code.com; liblit@well.sf.ca.us)
  16. ;;;
  17. ;;; This program is free software; you can redistribute it and/or
  18. ;;; modify it under the terms of the GNU General Public License as
  19. ;;; published by the Free Software Foundation; either version 1, or
  20. ;;; (at your option) any later version.
  21. ;;;
  22. ;;; This program is distributed in the hope that it will be useful,
  23. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  25. ;;; General Public License for more details.
  26. ;;;
  27. ;;; A copy of the GNU General Public License can be obtained from this
  28. ;;; program's author (send electronic mail to liblit@z-code.com or
  29. ;;; liblit@well.sf.ca.us) or from the Free Software Foundation, Inc.,
  30. ;;; 675 Mass Ave, Cambridge, MA 02139, USA.
  31.  
  32. ;;; Description:
  33. ;;;
  34. ;;; This package augments the standard GNUS kill file mechanism to
  35. ;;; allow expiring kill patterns.  Time stamps may be stored with
  36. ;;; patterns, and if a pattern's time stamp indicates that it has not
  37. ;;; been matched in a long period of time, that pattern is removed.
  38.  
  39. ;;; Installation:
  40. ;;;
  41. ;;; Optionally byte-compile expire-kill.el to expire-kill.elc and put
  42. ;;; them both in a directory on your load-path.  To load expire-kill
  43. ;;; when GNUS first up, add the following to your .emacs:
  44. ;;;
  45. ;;;   (setq gnus-startup-hook
  46. ;;;         '(lambda ()
  47. ;;;            (require 'expire-kill)))
  48. ;;;
  49. ;;; Autoloading based on the function "expire-kill" will *not* work
  50. ;;; properly, as expire-kill needs to hook itself into other parts of
  51. ;;; GNUS before the first kill file is loaded.
  52. ;;;
  53. ;;; Also, please note that expire-kill needs to use either Dave
  54. ;;; Gillespie's calc package or Edward Reingold's calendar package for
  55. ;;; performing date calculations.  The variable expire-date-package
  56. ;;; should be set to either 'calc or 'calendar, depending on which you
  57. ;;; wish to use.
  58.  
  59. ;;; Background and Motivation:
  60. ;;;
  61. ;;; The standard GNUS kill file mechanism is fairly powerful and
  62. ;;; flexible.  However, its usefulness is limited by the fact that
  63. ;;; kill patterns remain active indefinitely, unless manually removed
  64. ;;; by the user.  This makes certain uses of kill files highly
  65. ;;; impractical.
  66. ;;;
  67. ;;; For example, one might wish to use a subject-matching kill pattern
  68. ;;; to mark articles in a discussion thread that one is not interested
  69. ;;; in.  Using standard (gnus-kill ...), though, means that the
  70. ;;; pattern will remain in the kill file long after the thread itself
  71. ;;; has died out.  As time goes on, kill files will become bloated
  72. ;;; with patterns that have long ceased to be active.
  73. ;;;
  74. ;;; This package provides a time stamped alternative to "gnus-kill".
  75. ;;; The function "expire-kill" takes similar arguments, and performs
  76. ;;; the same function.  However, "expire-kill" also takes a time stamp
  77. ;;; argument (stored as a string) that indicates the last time its
  78. ;;; pattern was successfully matched.  Thus, instead of:
  79. ;;;
  80. ;;;   (gnus-kill "Subject" "cheese")
  81. ;;;
  82. ;;; in a kill file, one might see:
  83. ;;;
  84. ;;;   (expire-kill "Subject" "cheese" "<Tue Mar 2, 1993>")
  85. ;;;
  86. ;;; which would perform the same kill actions as "gnus-kill", but
  87. ;;; which additionally records that it hasn't actually seen a subject
  88. ;;; of "cheese" since March 2.
  89. ;;;
  90. ;;; A new method of applying kill files allows these time stamps to be
  91. ;;; updated when matches are made.  Other support functions sweep
  92. ;;; through a newsgroup's kill file and delete patterns that have not
  93. ;;; been matched in a long enough time (seven days, by default).
  94. ;;; Updated kill files are saved back to disk, or optionally deleted
  95. ;;; entirely if *all* their patterns have expired.
  96. ;;;
  97. ;;; Note that expire-kill is backward compatible with standard GNUS
  98. ;;; kill files.  Calls to "gnus-kill" and other elisp still work as
  99. ;;; before, and will never be expired.
  100.  
  101. ;;; Usage:
  102. ;;;
  103. ;;; To use this package, simply add calls to "expire-kill" to your
  104. ;;; GNUS kill files.  The first two arguments specify a header field
  105. ;;; and a regexp pattern, just as for "gnus-kill".  The third should
  106. ;;; be a string or integer that can be parsed into an initial time
  107. ;;; stamp.
  108. ;;;
  109. ;;; You should never need to construct these calls by hand, though.
  110. ;;; Instead, a suite of functions are provided that add calls, or
  111. ;;; portions of calls, for you.  All of these are suitable for calling
  112. ;;; via M-x, or for binding onto keys.  In fact, each function
  113. ;;; described below has a corresponding variable with the same name.
  114. ;;; If that variable is set to a string representing some sequence of
  115. ;;; keys, that key seuqence will be bound to evoke the corresponding
  116. ;;; function in the appropriate buffers.  These functions (and
  117. ;;; associated variables) are as follows:
  118. ;;;
  119. ;;;   - expire-summary-kill-same-subject
  120. ;;;   - expire-summary-kill-same-summary-and-select
  121. ;;;   - expire-summary-kill-thread
  122. ;;;   - expire-summary-kill-thread-and-select
  123. ;;;
  124. ;;; These functions should be used from the *Summary* buffer.  The
  125. ;;; first two functions add expiring kill patterns for the subject of
  126. ;;; the article at the cursor.  The second two functions add expiring
  127. ;;; kill patterns for followups to the article at the cursor.  All
  128. ;;; four functions mark any articles already in the *Summary* buffer
  129. ;;; that match their targets.  Furthermore, the "-and-select" forms
  130. ;;; immediately select the next unread article.
  131. ;;;
  132. ;;;   - expire-kill-file-kill-by-subject
  133. ;;;   - expire-kill-file-kill-by-thread
  134. ;;;
  135. ;;; These two functions may be used while editing a kill file.  They
  136. ;;; insert "expire-kill" calls to match the most recently seen subject
  137. ;;; and followups to the current article, respectively.
  138. ;;;
  139. ;;;   - expire-kill-file-insert-time-stamp
  140. ;;;
  141. ;;; This function is also intended for use while editing kill files.
  142. ;;; It will insert a time stamp corresponding to the present time
  143. ;;; after the cursor.  This can be useful for finishing up hand
  144. ;;; written calls to "expire-kill".
  145. ;;;
  146. ;;; By default, expire-summary-kill-same-summary-and-select is bound
  147. ;;; to "C-c C-k" and expire-summary-kill-thread-and-select is bound to
  148. ;;; "C-c k", both in the *Summary* buffer.  None of the others are
  149. ;;; bound by default, although this may easily be customized by
  150. ;;; setting the same-named variable to the desired key sequence.
  151.  
  152. ;;; Known Bugs and Limitations:
  153. ;;;
  154. ;;; For simplicity's sake, empty (whitespace only) kill files are not
  155. ;;; deleted until the next time their newsgroup is selected.  It might
  156. ;;; be nicer to delete empties as soon as the last s-expression is removed.
  157. ;;;
  158. ;;; Some users symlink kill files, using one such file for several
  159. ;;; related newsgroups.  We try to do right by these users, not
  160. ;;; deleting empty kill files that are also symlinks.  However,
  161. ;;; patterns may tend to expire more quickly when a kill file is
  162. ;;; shared.  If a pattern doesn't match in one group, it can be
  163. ;;; expired before it even gets to look at a second group.
  164.  
  165. ;;; Acknowledgments:
  166. ;;;
  167. ;;; Thanks for release 2.x go out to the many users who were kind
  168. ;;; enough to discuss and suggest improvements over earlier releases.
  169. ;;; In particular, Dave Disser's insightful correspondence has
  170. ;;; inspired many of 2.x's enhancements.  Don Wells, Bill Oakley, and
  171. ;;; Rod Whitby also deserve recognition for their suggestions, bug
  172. ;;; reports, and invaluable feedback.
  173.  
  174. ;;; LISPDIR ENTRY for the Elisp Archive
  175. ;;; 
  176. ;;;    LCD Archive Entry:
  177. ;;;    expire-kill|Ben Liblit|liblit@z-code.com|
  178. ;;;    expiring kill patterns for GNUS|
  179. ;;;    23-Nov-1993|2.5|~/misc/expire-kill.el.Z
  180.  
  181. ;;;; ------------------------------------------------------------
  182. ;;;; User customization variables.
  183. ;;;; ------------------------------------------------------------
  184.  
  185. (defvar expire-maximum-age 7
  186.   "*Longest time a pattern can go unmatched before being removed.
  187. The units on this measure are in days, and its value should be an
  188. integer.")
  189.  
  190.  
  191. (defvar expire-flush-frequency 'group
  192.   "*Determines how often modified kill file buffers are flushed.
  193. If set to the atom 'always, then flushing happens after every
  194. modification.  If set to 'group, then flushing occurs when you exit
  195. the current newsgroup.  If set to 'session, then modifications are
  196. flushed only when you exit GNUS itself.  Otherwise, no automatic
  197. actions are taken.
  198.  
  199. The variable expire-flush-action determines what it actually means to
  200. \"flush\" a modified kill file buffer.")
  201.  
  202.  
  203. (defvar expire-flush-action 'kill
  204.   "*Determines what to do with buffers of modified kill files.
  205. If set to the atom 'kill, then the modified buffer is saved and
  206. killed.  If set to 'save, then the modified buffer is saved, but is
  207. not killed.  Otherwise, no actions are taken.
  208.  
  209. The variable expire-flush-frequency determines how frequently the
  210. requested action is taken.")
  211.  
  212.  
  213. (defvar expire-simplify-subject t
  214.   "*Determines whether simplified subjects will be used as kill patterns.
  215. If nil, the original subject will be used in subject-based kill
  216. patterns.  Otherwise, the subject will first be simplified using
  217. gnus-simplify-subject.")
  218.  
  219.  
  220. (defvar expire-summary-kill-same-subject nil
  221.   "*Key sequence to which to bind the function of the same name.
  222. If set to a string representing a key sequence, that sequence will
  223. evoke the same-named function in the *Summary* buffer.  Otherwise, no
  224. binding will be made for this function.")
  225.  
  226. (defvar expire-summary-kill-same-summary-and-select "\C-c\C-k"
  227.   "*Key sequence to which to bind the function of the same name.
  228. If set to a string representing a key sequence, that sequence will
  229. evoke the same-named function in the *Summary* buffer.  Otherwise, no
  230. binding will be made for this function.")
  231.  
  232. (defvar expire-summary-kill-thread nil
  233.   "*Key sequence to which to bind the function of the same name.
  234. If set to a string representing a key sequence, that sequence will
  235. evoke the same-named function in the *Summary* buffer.  Otherwise, no
  236. binding will be made for this function.")
  237.  
  238. (defvar expire-summary-kill-thread-and-select "\C-ck"
  239.   "*Key sequence to which to bind the function of the same name.
  240. If set to a string representing a key sequence, that sequence will
  241. evoke the same-named function in the *Summary* buffer.  Otherwise, no
  242. binding will be made for this function.")
  243.  
  244. (defvar expire-kill-file-kill-by-subject "\C-c\C-x\C-s"
  245.   "*Key sequence to which to bind the function of the same name.
  246. If set to a string representing a key sequence, that sequence will
  247. evoke the same-named function in the kill file buffers.  Otherwise, no
  248. binding will be made for this function.")
  249.  
  250. (defvar expire-kill-file-kill-by-thread "\C-c\C-x\C-t"
  251.   "*Key sequence to which to bind the function of the same name.
  252. If set to a string representing a key sequence, that sequence will
  253. evoke the same-named function in the kill file buffers.  Otherwise, no
  254. binding will be made for this function.")
  255.  
  256. (defvar expire-kill-file-insert-time-stamp "\C-c\C-x\C-i"
  257.   "*Key sequence to which to bind the function of the same name.
  258. If set to a string representing a key sequence, that sequence will
  259. evoke the same-named function in the kill file buffers.  Otherwise, no
  260. binding will be made for this function.")
  261.  
  262.  
  263. (defvar expire-delete-empties 't
  264.   "*Determines whether or not empty kill files should be deleted.
  265. If set to the atom 'ask, then the user will be asked each time.  If
  266. set to some other non-nil value, then empties will be deleted
  267. automatically the first time they are applied to a newsgroup.  If nil,
  268. no deletions will be performed.")
  269.  
  270.  
  271. (defvar expire-kill-default-command '(gnus-summary-mark-as-read nil "x")
  272.   "*Default command to be evaluated by expire-kill if none is given.
  273. This must be a lisp expression or a string representing a key sequence.")
  274.  
  275.  
  276. (defvar expire-after-apply-hook (function
  277.                  (lambda ()
  278.                    (gnus-expunge "Xx")))
  279.   "*A hook called after all kill files have been applied.
  280. This hook is called after the global and local kill files have been
  281. applied to the currently-selected newsgroup.
  282.  
  283. If you used to finish off all of your kill files with a call to
  284. gnus-expunge, you can factor all of that out into this hook.  This
  285. reduces kill file size, and makes it easy to delete empty ones.")
  286.  
  287.  
  288. (defvar expire-load-hook nil
  289.   "*A hook called after expire-kill is loaded in.
  290. This can be a good place to put custom key bindings.")
  291.  
  292.  
  293. (defvar expire-date-package 'calendar
  294.   "*The name of the package to use for date calculations.
  295. This should be set to an atom for which expire-date-package-profiles
  296. has an entry, and which may be loaded using (require ...).  Currently
  297. supported packages include calc and calendar.
  298.  
  299. Note that stamps may be read from either format, assuming both are
  300. available.  New stamps, however, will always be generated using the
  301. selected package.")
  302.  
  303.  
  304. (defvar expire-date-package-profiles
  305.   '((calendar (calendar-absolute-from-gregorian (calendar-current-date))
  306.           (- (calendar-absolute-from-gregorian (calendar-current-date))
  307.          timestamp))
  308.     (calc (calc-eval "floor(now())")
  309.       (calc-eval "floor(now() - $)" 'raw timestamp)))
  310.   "*A list of profiles of known date calculation packages.
  311. Each profile is, itself, a list of three values.
  312.  
  313.  - The first is the package name as an atom, which may be used in
  314.    expire-date-package and which is used in (require ...) at load
  315.    time.
  316.  
  317.  - The second is an s-expression that returns the current date.
  318.  
  319.  - The third is an s-expression that returns the (integral) difference
  320.    between the date stored in the variable \"timestamp\" and the
  321.    current date.")
  322.  
  323.  
  324. (defconst expire-version "$Revision: 2.5 $"
  325.   "The current version, of revision number, of expire-kill.
  326. Be sure to include this in any bug reports.")
  327.  
  328. ;;;; ------------------------------------------------------------
  329. ;;;; Dependencies.
  330. ;;;; ------------------------------------------------------------
  331.  
  332. ;;; We need to plug into several GNUS hooks, and add-hook is the
  333. ;;; cleanest way to do so.  Several implementations exist; any should
  334. ;;; suffice.  Some Emacsi may have add-hook built in.
  335.  
  336. (or (fboundp 'add-hook)
  337.     (require 'add-hook))
  338.  
  339. ;;; Backquoting is used to construct the augmented commands that
  340. ;;; expire-kill passes down to gnus-kill.
  341.  
  342. (require 'backquote)
  343.  
  344. ;;; All of expire-kill's date calculations are handled by outside
  345. ;;; packages.  Currently supported are Dave Gillespie's winning calc
  346. ;;; package, and Edward Reingold's equally winning calendar package.
  347. ;;; If you don't have either, you should.  Inquire at your
  348. ;;; neighborhood elisp archive.
  349.  
  350. ;;; Note that if you are using calendar and receive errors about a
  351. ;;; void function named current-time-zone, you will need to initialize
  352. ;;; some of calendar's variables for it.  Those variables are
  353. ;;; calendar-time-zone, calendar-standard-time-zone-name, and
  354. ;;; calendar-daylight-time-zone-name.  See calendar.el for further
  355. ;;; information.
  356.  
  357. (and (boundp 'expire-date-package)
  358.      (require expire-date-package))
  359.  
  360. ;;; Common Lisp defines a really handy (case...) form, that we use for
  361. ;;; checking the value of various user options.  We also use its
  362. ;;; convenient push and pop functions.
  363.  
  364. (require 'cl)
  365.  
  366. ;;; Normally, GNUS should already be loaded by the time we are loaded.
  367. ;;; Just in case, though, make sure it is there.
  368.  
  369. (require 'gnus)
  370.  
  371. ;;;; ------------------------------------------------------------
  372. ;;;; Internal-use variables.
  373. ;;;; ------------------------------------------------------------
  374.  
  375. (defvar expire-current-kill-buffer nil
  376.   "The buffer of the kill file currently (or recently) being applied.")
  377.  
  378. (defvar expire-modified-buffers nil
  379.   "A list of all kill file buffers that may have been modified recently.")
  380.  
  381.  
  382. (defun expire-current-date ()
  383.   "Return some representation of today's date."
  384.   (eval (nth 1 (assq expire-date-package
  385.            expire-date-package-profiles))))
  386.  
  387.  
  388. (defun expire-days-since (timestamp)
  389.   "Return the integral number of days between today and TIMESTAMP."
  390.   (eval (nth 2 (assq expire-date-package
  391.              expire-date-package-profiles))))
  392.  
  393. ;;;; ------------------------------------------------------------
  394. ;;;; User functions.
  395. ;;;; ------------------------------------------------------------
  396.  
  397. (defun expire-kill (field pattern timestamp &optional command all)
  398.   "If FIELD of an article matches REGEXP, update TIMESTAMP and execute COMMAND.
  399. If no COMMAND is given, the value of expire-kill-default-command is
  400. used.  If optional 5th argument ALL is non-nil, articles marked are
  401. also applied to.  If FIELD is an empty string (or nil), entire article
  402. body is searched for.  COMMAND must be a lisp expression or a string
  403. representing a key sequence."
  404. (let (successful)
  405.   (let ((command (or command expire-kill-default-command)))
  406.     (gnus-kill field pattern
  407.            (` (progn (setq successful t)
  408.              (, (if (stringp command)
  409.                 (list 'execute-kbd-macro command)
  410.                   command))))
  411.            all))
  412.   (if successful
  413.       (expire-restamp field pattern
  414.               (expire-convert-timestamp timestamp)
  415.               command all)
  416.     (expire-filter (expire-convert-timestamp timestamp)))))
  417.  
  418.  
  419. (defun expire-kill-file-kill-by-subject (ask)
  420.   "Insert expiring KILL command for current subject.
  421. Argument ASK non-nil (C-u if called interactively) allows the user to
  422. edit the pattern before it is inserted."
  423.   (interactive "P")
  424.   (let ((subject (if gnus-current-kill-article
  425.              (gnus-header-subject
  426.               (gnus-find-header-by-number
  427.                gnus-newsgroup-headers
  428.                gnus-current-kill-article))
  429.            "")))
  430.     (expire-insert-kill "Subject"
  431.             (regexp-quote
  432.              (if expire-simplify-subject
  433.                  (gnus-simplify-subject subject)
  434.                subject))
  435.             ask)))
  436.  
  437.  
  438. (defun expire-kill-file-kill-by-thread (ask)
  439.   "Insert expiring KILL command for current thread.
  440. Argument ASK non-nil (C-u if called interactively) allows the user
  441. to edit the pattern before it is inserted."
  442.   (interactive "P")
  443.   (expire-insert-kill "References"
  444.               (if gnus-current-kill-article
  445.               (regexp-quote
  446.                (gnus-header-id
  447.                 (gnus-find-header-by-number
  448.                  gnus-newsgroup-headers
  449.                  gnus-current-kill-article)))
  450.             "")
  451.               ask))
  452.  
  453.  
  454. (defun expire-kill-file-insert-time-stamp ()
  455.   "Insert a time stamp for the current date after point.
  456. Handy for finishing up hand written calls to expire-kill."
  457.   (interactive)
  458.   (prin1 (expire-current-date)
  459.      (current-buffer)))
  460.  
  461.  
  462. (defun expire-summary-kill-same-subject (ask)
  463.   "Add a local, expiring kill pattern for the current subject.
  464. Also, mark all articles with this subject in the current buffer as
  465. read, but do not select the next unread article.  Argument ASK non-nil
  466. (C-u if called interactively) allows the user to edit the pattern
  467. before it is inserted."
  468.   (interactive "P")
  469.   (expire-summary-kill-using 'expire-kill-file-kill-by-subject ask)
  470.   (gnus-summary-kill-same-subject nil))
  471.  
  472.  
  473. (defun expire-summary-kill-same-summary-and-select (ask)
  474.   "Add a local, expiring kill pattern for the current subject.
  475. Also, mark all articles with this subject in the current buffer as
  476. read and select the next unread article.  Argument ASK non-nil (C-u if
  477. called interactively) allows the user to edit the pattern before it is
  478. inserted."
  479.   (interactive "P")
  480.   (expire-summary-kill-using 'expire-kill-file-kill-by-subject ask)
  481.   (gnus-summary-kill-same-subject-and-select nil))
  482.  
  483.  
  484. (defun expire-summary-kill-thread (ask)
  485.   "Add a local, expiring kill pattern for the current thread.  Also,
  486. mark all articles in the current thread as read.  Argument ASK non-nil
  487. (C-u if called interactively) allows the user to edit the pattern
  488. before it is inserted."
  489.   (interactive "P")
  490.   (expire-summary-kill-using 'expire-kill-file-kill-by-thread ask)
  491.   (gnus-summary-kill-thread nil))
  492.  
  493.  
  494. (defun expire-summary-kill-thread-and-select (ask)
  495.   "Add a local, expiring kill pattern for the current thread.  Also,
  496. mark all articles in the current thread as read and select the next
  497. unread article.  Argument ASK non-nil (C-u if called interactively)
  498. allows the user to edit the pattern before it is inserted."
  499.   (interactive "P")
  500.   (expire-summary-kill-using 'expire-kill-file-kill-by-thread ask)
  501.   (gnus-summary-kill-thread nil)
  502.   (if (memq (gnus-summary-article-number)
  503.         gnus-newsgroup-unreads)
  504.       (gnus-summary-select-article)
  505.     (gnus-summary-next-unread-article)))
  506.  
  507. ;;;; ------------------------------------------------------------
  508. ;;;; Internal-use hook functions.
  509. ;;;; ------------------------------------------------------------
  510.  
  511. (defun expire-apply-kill-file ()
  512.   "Apply kill files to the current newsgroup.  The global kill file,
  513. if it exists, is loaded in the standard manner.  The local kill file,
  514. however, is read and evaluated one s-expression at a time.  This
  515. allows calls to \"expire-kill\" to modify themselves.  If the local
  516. kill file consists of nothing but whitespace, it may be deleted,
  517. depending upon the value of expire-delete-empties.
  518.  
  519. The hook expire-after-apply-hook is executed after both the global and
  520. local kill files have been applied.  If neither the global nor the
  521. local kill file actually existed, though, this hook is ignored."
  522.   (let (kill-files-applied)
  523.     (let ((global-kill-file (gnus-newsgroup-kill-file nil)))
  524.       (if (file-readable-p global-kill-file)
  525.       (progn
  526.         (message "Loading %s..." global-kill-file)
  527.         (load (gnus-newsgroup-kill-file nil) 'noerr nil 'nosufx)
  528.         (message "Loading %s...done" global-kill-file)
  529.         (setq kill-files-applied 't))))
  530.     (let ((local-kill-file (gnus-newsgroup-kill-file gnus-newsgroup-name)))
  531.       (if (or (file-readable-p local-kill-file)
  532.           (get-file-buffer local-kill-file))
  533.       (save-excursion
  534.         (message "Loading %s..." local-kill-file)
  535.         (find-file local-kill-file)
  536.         (push (setq expire-current-kill-buffer (current-buffer))
  537.           expire-modified-buffers)
  538.         (goto-char (point-min))
  539.         (if (re-search-forward "[^ \t\r\n\f]" nil 'noerr)
  540.         (progn
  541.           (goto-char (point-min))
  542.           (condition-case nil
  543.               (expire-eval-buffer local-kill-file)
  544.             (end-of-file))
  545.           (setq kill-files-applied 't)
  546.           (message "Loading %s...done" local-kill-file)
  547.           (bury-buffer expire-current-kill-buffer)
  548.           (expire-possibly-flush 'always))
  549.           (if (and expire-delete-empties
  550.                (not (file-symlink-p local-kill-file))
  551.                (if (eq expire-delete-empties 'ask)
  552.                (y-or-n-p
  553.                 (format "Delete empty %s " local-kill-file))
  554.              t))
  555.           (progn
  556.             (message "Deleting %s..." local-kill-file)
  557.             (delete-file local-kill-file)
  558.             (set-buffer-modified-p nil)
  559.             (kill-buffer nil)
  560.             (message "Deleting %s...done" local-kill-file))
  561.         (bury-buffer expire-current-kill-buffer)
  562.         (expire-possibly-flush 'always))))))
  563.     (and kill-files-applied
  564.      (run-hooks 'expire-after-apply-hook))))
  565.  
  566.  
  567. (defun expire-exit-group ()
  568.   "Possibly flush all modified buffers.
  569. Should be called from gnus-exit-group-hook."
  570.   (expire-possibly-flush 'group))
  571.  
  572.  
  573. (defun expire-exit-gnus ()
  574.   "Possibly flush all modified buffers.
  575. Should be called from gnus-exit-gnus-hook."
  576.   (expire-possibly-flush 'session))
  577.  
  578. ;;;; ------------------------------------------------------------
  579. ;;;; Internal-use buffer functions.
  580. ;;;; ------------------------------------------------------------
  581.  
  582. (defun expire-eval-buffer (name)
  583.   "Evaluate the s-expressions following point in the current buffer,
  584. one at a time.  NAME gives the buffer's displayed name."
  585.   ;; Note: while this function could be quite elegant if written
  586.   ;; tail-recursively, even optimizing byte-compilers have difficulty
  587.   ;; making tail-recursion as efficient as a flat loop.  This is
  588.   ;; primarily due to Lisp's dynamic scoping.
  589.   (while 't
  590.     (set-buffer gnus-summary-buffer)
  591.     (eval (read expire-current-kill-buffer))
  592.     (set-buffer expire-current-kill-buffer)
  593.     (message "Loading %s...%d%%"
  594.          name
  595.          (/ (* 100 (point))   ;; Re-evaluate (point-max) each time,
  596.         (point-max)))))   ;; as the buffer can change in size.
  597.  
  598.  
  599. (defun expire-possibly-flush (frequency)
  600.   "Possibly flush modified kill file buffers.
  601. Flushing actually happens only if argument FREQUENCY and the variable
  602. expire-flush-frequency are the same."
  603.   (if (eq frequency expire-flush-frequency)
  604.       (expire-flush)))
  605.  
  606.  
  607. (defun expire-flush ()
  608.   "Flush modified buffers as called for by expire-flush-action.
  609. Depending on the value of this variable, we either save and kill the
  610. buffers in expire-modified-buffers just save them, or don't do
  611. anything.  Also, reset expire-modified-buffers to nil when we are done."
  612.   ;; Note: while this function could be quite elegant if written
  613.   ;; tail-recursively, even optimizing byte-compilers have difficulty
  614.   ;; making tail-recursion as efficient as a flat loop.  This is
  615.   ;; primarily due to Lisp's dynamic scoping.
  616.   (while expire-modified-buffers
  617.     (let ((buffer (pop expire-modified-buffers)))
  618.       (if (buffer-name buffer)
  619.       (case expire-flush-action
  620.         (kill (expire-save-if-modified buffer)
  621.            (kill-buffer buffer))
  622.         (save (expire-save-if-modified buffer)))))))
  623.  
  624.  
  625. (defun expire-save-if-modified (buffer)
  626.   "Save BUFFER, but only if it has been modified.
  627. This prevents unsightly \"(No changes need to be saved)\" messages."
  628.   (if (buffer-modified-p buffer)
  629.       (save-excursion
  630.     (set-buffer buffer)
  631.     (save-buffer)
  632.     (bury-buffer))))
  633.  
  634. ;;;; ------------------------------------------------------------
  635. ;;;; Internal-use pattern functions.
  636. ;;;; ------------------------------------------------------------
  637.  
  638. (defun expire-convert-timestamp (timestamp)
  639.   "Convert a time stamp to the user's preferred format.
  640. If TIMESTAMP is a string, it is assumed to be in calc format; integers
  641. are assumed to belong to calendar.  An equivalent string or integer is
  642. returned, depending upon the value of expire-date-package."
  643.   (cond ((integerp timestamp)
  644.      (case expire-date-package
  645.        (calendar timestamp)
  646.        (calc (require 'calendar)
  647.          (calc-eval "date($)" nil (1+ timestamp)))))
  648.     ((stringp timestamp)
  649.      (case expire-date-package
  650.        (calc timestamp)
  651.        (calendar (require 'calc)
  652.              (1- (calc-eval "date($)" 'raw timestamp)))))))
  653.  
  654.  
  655. (defun expire-restamp (field pattern timestamp &optional command all)
  656.   "Replace a call to \"expire-kill\" with one having an updated time
  657. stamp.  The s-expression before the point is deleted, and a new one is
  658. inserted that calls \"expire-kill\" with the given FIELD and REGEXP,
  659. and the current time as its time stamp.  If the current time is not
  660. different from TIMESTAMP, however, the buffer is not modified.
  661. Optional arguments COMMAND and ALL correspond to those passed to the
  662. original expire-kill call, and if given will be reproduced in the new
  663. call."
  664. (let ((now (expire-current-date)))
  665.   (or (equal timestamp now)
  666.       (progn (set-buffer expire-current-kill-buffer)
  667.          (backward-sexp)
  668.          (kill-sexp 1)
  669.          (delete-blank-lines)
  670.          (delete-blank-lines)
  671.          (expire-insert-kill field pattern nil now command all)))))
  672.  
  673.  
  674. (defun expire-insert-kill (field pattern ask &optional timestamp command all)
  675.   "General purpose function to produce \"expire-kill\" calls.
  676. Inserts a call to \"expire-kill\" with the given FIELD and REGEXP.  If
  677. third argument ASK is non-nil, the user will be allowed to edit the
  678. regexp. An optional fourth argument provides the TIMESTAMP; if none is
  679. given, a stamp for the current time is used.  Fifth and sixth optional
  680. arguments COMMAND and ALL specify the corresponding optional arguments
  681. to the expire-kill call."
  682.   (prin1 (append (list 'expire-kill
  683.                field
  684.                (if ask
  685.                (read-from-minibuffer (concat field ":  ")
  686.                          pattern)
  687.              pattern)
  688.                (or timestamp
  689.                (expire-current-date)))
  690.          (cond (all (list command all))
  691.                (command (list command))))
  692.      (current-buffer))
  693.   (or (eolp)
  694.       (insert ?\n)))
  695.  
  696.  
  697. (defun expire-filter (timestamp)
  698.   "Delete outdated calls to \"expire-kill.\"
  699. If TIMESTAMP is older than the age limit given by expire-maximum-age,
  700. delete the s-expression before the point.  Presumably, this
  701. corresponds to an outdated \"expire-kill\" call."
  702.   (if (> (expire-days-since timestamp)
  703.       expire-maximum-age)
  704.       (progn
  705.     (set-buffer expire-current-kill-buffer)
  706.     (let ((end-of-sexp (point)))
  707.       (backward-sexp)
  708.       (delete-region (point) end-of-sexp)
  709.       (delete-blank-lines)
  710.       (delete-blank-lines)))))
  711.  
  712.  
  713. (defun expire-summary-kill-using (kill-mode-function ask)
  714.   "In the *Summary* buffer, add a new expiring kill pattern.
  715. First argument FUNCTION should be the name of a function to be called,
  716. with no arguments, in the local kill file to actually insert the new
  717. pattern.  If second argument ASK is non-nil, allow the user to edit
  718. the kill pattern before it is inserted."
  719.   (let* ((gnus-current-kill-article (gnus-summary-article-number))
  720.      (kill-file (gnus-newsgroup-kill-file gnus-newsgroup-name))
  721.      (kill-directory (file-name-directory kill-file)))
  722.     (save-window-excursion
  723.       (or (file-exists-p kill-directory)
  724.       (make-directory kill-directory))
  725.       (find-file kill-file)
  726.       (goto-char (point-min))
  727.       (funcall kill-mode-function ask)
  728.       (push (current-buffer) expire-modified-buffers)
  729.       (bury-buffer)
  730.       (expire-possibly-flush 'always))))
  731.  
  732. ;;;; ------------------------------------------------------------
  733. ;;;; Initialization.
  734. ;;;; ------------------------------------------------------------
  735.  
  736. ;;; Bind each of the major user-callable functions, if the same-named
  737. ;;; variable is set to a string representing a key sequence.
  738.  
  739. (mapcar (function
  740.      (lambda (binding)
  741.        (let ((function (car binding))
  742.          (keymap (cdr binding)))
  743.          (if (stringp (symbol-value function))
  744.          (define-key
  745.            (symbol-value keymap)
  746.            (symbol-value function)
  747.            function)))))
  748.     '((expire-summary-kill-same-subject . gnus-summary-mode-map)
  749.       (expire-summary-kill-same-summary-and-select . gnus-summary-mode-map)
  750.       (expire-summary-kill-thread . gnus-summary-mode-map)
  751.       (expire-summary-kill-thread-and-select . gnus-summary-mode-map)
  752.       (expire-kill-file-kill-by-subject . gnus-kill-file-mode-map)
  753.       (expire-kill-file-kill-by-thread . gnus-kill-file-mode-map)
  754.       (expire-kill-file-insert-time-stamp . gnus-kill-file-mode-map)))
  755.     
  756.  
  757. ;;; Install ourselves as the kill file applier of choice.
  758.  
  759. (setq gnus-apply-kill-hook 'expire-apply-kill-file)
  760.  
  761.  
  762. ;;; Plug in to some exit conditions that might prompt flushing.
  763.  
  764. (add-hook 'gnus-exit-group-hook 'expire-exit-group)
  765. (add-hook 'gnus-exit-gnus-hook 'expire-exit-gnus)
  766.  
  767.  
  768. ;;; Announce our presence and call any user hooks.
  769.  
  770. (provide 'expire-kill)
  771. (run-hooks 'expire-load-hook)
  772.  
  773.  
  774. ;;; The End
  775.